#include "BufferCache.h"

extern BufferCache * cache;


int fHash(int bloque) { //dado fs, bloque devuelve numero de lista hash
   return bloque % MAX_HASH;
}

Buffer getBufferNull() { //devuelve un buffer nulo (inicializado)
   Buffer buf; //buffer nulo
   buf.idbloque.fs = NIL;
   buf.idbloque.inodo = NIL;
   buf.idbloque.bloque = NIL;

   buf.datosValidos = 0;
   buf.bloqueado = 0;
   buf.delayWrite = 0;

   buf.fd = NIL;
   buf.pid = NIL; //esto es para que no haga kill(0, señal): se cargaria del sistema!!
   buf.nbytesValidos = 0;

   cache->nslept = 0;
   for (int i = 0; i < MAX_SLEPT; i++)
      buf.slept[i] = NIL;

   buf.hashNext = NIL;
   buf.freeListPre = NIL;
   buf.freeListNext = NIL;

   return buf;
}

bool bc_freeListVacia() {
   if ((cache->freeStart == NIL) || (cache->tamFree == 0)) { //ambas deberian pasar a la vez (p <-> q)
      cache->freeStart = NIL; //para asegurarse
      return true;
   }
   
   return false;
}


bool bc_freeListLlena() {
   return cache->tamFree == MAX_BUFFERS;
}


bool bc_hashVacia(int nlista) {
   //no hace falta comprobar rango de nlista
   if ((cache->hashStart[nlista] == NIL) || (cache->tamHash[nlista] == 0)) { //ambas deberian pasar a la vez (p <-> q)
      cache->hashStart[nlista] = NIL;
      return true;
   }

   return false;
}

bool bc_hashLlena(int nlista) {
   return cache->tamHash[nlista] == MAX_BUFFERS; //puede pasar que todas las colisiones esten en una sola lista, y resto vacia
}

//Nota: las acciones sobre freelist es solo mover punteros, los buffers siguen igual en hash

bool bc_insertFreeEnd(int nbuf) { //insertar elemento al final de la lista libre
   if (nbuf == NIL) {
      chivato("insertFreeEnd: nbuf=NIL\n");
      return false;
   }
   if (bc_findFree(nbuf) >= 0) { //si ya estaba
      chivato("insertFreeEnd: ya estaba antes\n");
      return false;
   }
   
   if (bc_freeListLlena()) {
      printf("insertFreeEnd: freeListLlena\n"); //no deberia de pasarrr gracias a getblk()
      return false;
   }

   //buf.bloqueado = 0, se hace desde fuera
   //se obvia el que caso de que freelist este llena (todos los buffers de la cache estan libres)
   //no hace falta poner hashNext = NIL; porque insertFreeEnd() se llama dentro de extractHash()
   if (bc_freeListVacia()) {
      cache->freeStart = nbuf; //el unico de la lista
      cache->bufs[nbuf].freeListNext = nbuf; //se apunta a si mismo por ambos lados
      cache->bufs[nbuf].freeListPre = nbuf;
      cache->freeEnd = nbuf;
   }
   else {
      int lastOld = cache->freeEnd;
      cache->bufs[nbuf].freeListNext = cache->freeStart;
      cache->bufs[nbuf].freeListPre = lastOld;

      cache->bufs[lastOld].freeListNext = nbuf;
      cache->bufs[cache->freeStart].freeListPre = nbuf;

      cache->freeEnd = nbuf;
   }

   cache->tamFree++;
   return true;
}

bool bc_insertFreeFirst(int nbuf) { //ANADIDO: insertar buffer al principio de la lista libre
   /*if (nbuf == NIL) {
      chivato("insertFreeFirst: nbuf=NIL\n");
      return false;
   }*/

   if (bc_findFree(nbuf) >= 0) { //si ya estaba
      chivato("insertFreeFirst: ya estaba antes\n");
      return false;
   }

   if (bc_freeListVacia()) {
      cache->freeStart = nbuf; //el unico de la lista
      cache->bufs[nbuf].freeListNext = nbuf; //se apunta a si mismo por ambos lados
      cache->bufs[nbuf].freeListPre = nbuf;
      cache->freeEnd = nbuf;
   }
   else {
      int firstOld = cache->freeStart;
      cache->bufs[nbuf].freeListPre = cache->freeEnd;
      cache->bufs[nbuf].freeListNext = firstOld;

      cache->bufs[firstOld].freeListPre = nbuf;
      cache->bufs[cache->freeEnd].freeListNext = nbuf;

      cache->freeStart = nbuf;
   }

   cache->tamFree++;
   return true;
}

bool bc_extractFree(int nbuf) { // extrae elemento especificado de la lista libre (cualquier posicion)
   //se quita de free_list para poder ser usado. Aun sigue en hash
   /*if (nbuf == NIL) {
      chivato("extractFree: nbuf=NIL\n");
      return false;
   }*/

   if (bc_freeListVacia())
      return false;

   if (cache->freeStart == cache->freeEnd) { //si solo hay un elemento
      if (nbuf == cache->freeStart) {//encontrado
         cache->bufs[nbuf].freeListPre = NIL;
         cache->bufs[nbuf].freeListNext = NIL;
         cache->freeStart = NIL;
         cache->freeEnd = NIL;
      }
      cache->tamFree--;
      return true;
   }

   int nbuf2 = cache->freeStart;
   while (1) { //no encontrado
      if (nbuf2 == nbuf) { //encontrado

         int pre = cache->bufs[nbuf].freeListPre;
         int next = cache->bufs[nbuf].freeListNext;

         cache->bufs[pre].freeListNext = next;
         cache->bufs[next].freeListPre = pre;

         cache->bufs[nbuf].freeListPre = NIL;
         cache->bufs[nbuf].freeListNext = NIL;

         if (nbuf == cache->freeStart)
            cache->freeStart = next;
         else
            if (nbuf == cache->freeEnd)
            cache->freeEnd = pre;

         cache->tamFree--;
         return true;
      } //if encontrado

      else {
         nbuf2 = cache->bufs[nbuf2].freeListNext;
         if (nbuf2 == cache->freeStart) //ya ha dado una vuelta: no esta
            return false;
      }
         
   } //while
}

int bc_findFree(int nbuf) { //devuelve nbuffer de lista libre. si no NIL
   if (nbuf == NIL) {
      return NIL;
   }

   if (bc_freeListVacia())
      return NIL;

   int nbuf2 = cache->freeStart;
   while (1) { //no encontrado
      if (nbuf2 == nbuf)  //encontrado
         return nbuf2;

      else {
         nbuf2 = cache->bufs[nbuf2].freeListNext;
         if (nbuf2 == cache->freeStart) //ya ha dado una vuelta: no esta
            return NIL;
      }
   } //while
}

int bc_findHash(int fs, int inodo, int bloque) { //devuelve nbuffer de hash. si no NIL
   int nlista = fHash(bloque);
   
   int nbuf = cache->hashStart[nlista];
   if (bc_hashVacia(nlista))
      return NIL;

   while (nbuf != NIL) { //  no encontrado
      if ((cache->bufs[nbuf].idbloque.fs == fs) && (cache->bufs[nbuf].idbloque.inodo == inodo) \
         && (cache->bufs[nbuf].idbloque.bloque == bloque))
         return nbuf;

      nbuf = cache->bufs[nbuf].hashNext;
      /*cont++;
      if ((cont > MAX_BUFFERS + 2) && (nbuf != NIL)) {
         printf("¡¡Lista hash %d CORRUPTA!!\n", nlista);
         exit(0);
      }*/
   }

   return NIL;
}


int bc_findHash(int nlista, int nbuf) { //devuelve nbuffer de la lista hash especificada, si no NIL
   if (bc_hashVacia(nlista))
      return NIL;

   int nbuf2 = cache->hashStart[nlista];
   while (nbuf2 != NIL) { //  no encontrado
      if (nbuf == nbuf2) //encontrado
         return nbuf2;

      nbuf2 = cache->bufs[nbuf2].hashNext;
      /*cont++;
      if ((cont > MAX_BUFFERS + 2) && (nbuf != NIL)) {
         printf("¡¡Lista hash %d CORRUPTA!!\n", nlista);
         exit(0);
      }*/
   } //while

   return NIL;
}

//Nota: insertHash y extractHash solo en: Escenario 2: no esta en hash y primero de freelist no esta DW (ver getblk())

int bc_insertHash(int fs, int inodo, int bloque) { //inserta al principio de una lista hash. devuelve nbuffer
   //aqui se garantiza (getblk()) que freelist no esta vacia y que DW = 0
   //int nbuf = bc_findHash(fs, inodo, bloque); //no hace falta pues se garantiza siempre que no este en hash (ver getblk())
   int nlista = fHash(bloque);
   /*if (bc_hashLlena(nlista)) {
      printf("pid=%d. insertHash: hash %d llena\n", getpid(), nlista);
      return NIL;
   }*/

   int nbuf = cache->freeStart; //primero de la lista libre
   bc_extractFree(nbuf);

   int first = cache->hashStart[nlista];

   if (bc_findHash(nlista, nbuf) == NIL) { //si no estaba en esta lista hash
      cache->bufs[nbuf].hashNext = first; //first puede ser tambien NIL
      cache->hashStart[nlista] = nbuf;
      cache->tamHash[nlista]++;
   }

   cache->bufs[nbuf].idbloque.fs = fs; //actualiza buffer
   cache->bufs[nbuf].idbloque.inodo = inodo;
   cache->bufs[nbuf].idbloque.bloque = bloque;
   cache->bufs[nbuf].datosValidos = 0; //tambien se podria hacer desde fuera, en getblk()
   cache->bufs[nbuf].nbytesValidos = 0;
   cache->bufs[nbuf].fd = NIL;
   return nbuf;
}

bool bc_extractHash(int nbuf) { //extrae de una lista hash el buffer especificado (cualquier posicion)
   if (nbuf == NIL) {
      chivato("extractHash: nbuf=NIL\n");
      return false;
   }
   
   bool borrado = false;
   for (int nlista = 0; nlista < MAX_HASH; nlista++) { //busca en todas las listas hash
      if (bc_hashVacia(nlista)) //para asegurarse
         continue;

      int nbuf2 = cache->hashStart[nlista];
      int pre = NIL; //el anterior
      //int cont = 0; //contador para controlar corrupcion de la hash
      while (nbuf2 != NIL) { 
         if (nbuf2 == nbuf) { //encontrado
            
            if (nbuf == cache->hashStart[nlista]) { //es el primero
               if (cache->tamHash[nlista] == 1) //solo hay un elemento
                  cache->hashStart[nlista] = NIL;
               else
                  cache->hashStart[nlista] = cache->bufs[nbuf].hashNext; //hashNext puede tambien NIL
            }
            else if (cache->bufs[nbuf].hashNext == NIL) //es el ultimo
               cache->bufs[pre].hashNext = NIL;
            else
               cache->bufs[pre].hashNext = cache->bufs[nbuf].hashNext; //esta en medio
               
            cache->bufs[nbuf].hashNext = NIL; //en cualquier caso
            //XXX: noo: bc_insertFreeEnd(nbuf);
            if (!borrado)
               cache->tamHash[nlista]--; //solo se decrementa la primera vez que se encuentra
            borrado = true; //return borrado;
            break; //sale del while. siguiente lista //busca en mas listas por si acaso que este repetido. aunque esto no deberia de pasar
         } //if

         pre = nbuf2; //se mueve a siguiente
         nbuf2 = cache->bufs[nbuf2].hashNext;
         /*cont++;
         if ((cont > MAX_BUFFERS + 2) && (nbuf != NIL)) {
            printf("¡¡Lista hash %d CORRUPTA!!\n", nlista);
            exit(0);
         } */
      } //while

   }

   return borrado;
}

void bc_init() { //inicializacion de la memoria cache
   int i; //la hash se inicializa vacia y lista libre llena
	for (i = 0; i < MAX_HASH; i++) {
      cache->hashStart[i] = NIL;
      cache->tamHash[i] = 0;
	}

   for (i = 0; i < MAX_SLEPT; i++)
      cache->bc_slept[i] = NIL;

   cache->nslept = 0;
   cache->freeStart = 0; //inicio de la free_list es el primero
   cache->freeEnd = MAX_BUFFERS - 1; //fin de la free_list es el ultimo

   Buffer buf = getBufferNull(); //buffer nulo

   for (i = 1; i < MAX_BUFFERS - 1; i++) //del segundo al penultimo
      cache->bufs[i] = buf; //se inicializa todo a nulos

   //inicializa lista libre con todos ordenados y nulos
   for (i = 1; i < MAX_BUFFERS - 1; i++) { //del segundo al penultimo
      cache->bufs[i] = buf; //se inicializa todo a nulos
      cache->bufs[i].freeListNext = i + 1;
      cache->bufs[i].freeListPre = i - 1;
   }

   //el primero buffer
   cache->bufs[0] = buf;
   cache->bufs[0].freeListNext = 1;
   cache->bufs[0].freeListPre = MAX_BUFFERS - 1; //el ultimo, xq es lista circular

   //el ultimo buffer
   cache->bufs[MAX_BUFFERS - 1] = buf;
   cache->bufs[MAX_BUFFERS - 1].freeListNext = 0; //el primero, xq es lista circular
   cache->bufs[MAX_BUFFERS - 1].freeListPre = MAX_BUFFERS - 2;
   
   //extra
   cache->tamFree = MAX_BUFFERS;
}

void buffer_print(int nbuf) { //imprime un buffer
   //printf("[bloque=%d, bloqueado=%d, datosValidos=%d, delayWrite=%d]\n", buf.idbloque.bloque, buf.bloqueado, buf.datosValidos, buf.delayWrite);
   if (cache->bufs[nbuf].idbloque.bloque == NIL)
      printf("[nbuf=%d], ", nbuf);
   else
      printf("[nbuf=%d, bld=%d,i=%d,b=%d], ", \
         nbuf, cache->bufs[nbuf].bloqueado, cache->bufs[nbuf].idbloque.inodo, cache->bufs[nbuf].idbloque.bloque);
}

void bc_print() { //imprime la buffer cache
   //muestra lista libre
   printf("\n pid=%d imprime BUFFER CACHE:\n freeStart = %d, freeEnd = %d \n Lista Libre:\n", getpid(), cache->freeStart, cache->freeEnd);
   int nbuf = cache->freeStart;
   int cont = 0; //contador para comprobar corrupcion de la buffercache
   while (1) {
      buffer_print(nbuf);
      nbuf = cache->bufs[nbuf].freeListNext;
      cont++;

      if (nbuf == cache->freeStart) //ya ha dado la vuelta, aqui tambien se contempla si esta vacia
         break;

      if ((cont > MAX_BUFFERS + 2) || (nbuf == NIL)) { //freelist es circular, con lo que no deberia haber NIL
         printf("¡¡FREELIST CORRUPTA!!\n");
         bc_print();
         break;
      }
   }
   printf(". Cont=%d. Tam=%d\n", cont, cache->tamFree);

   //muestra hash
   for (int nlista = 0; nlista < MAX_HASH; nlista++)  {
      printf("\npid=%d. Lista hash %d:\n", getpid(), nlista);
      nbuf = cache->hashStart[nlista];
      cont = 0;
      while (nbuf != NIL) { //hasta fin de lista hash
         buffer_print(nbuf);
         nbuf = cache->bufs[nbuf].hashNext;
         cont++;
         if ((cont > MAX_BUFFERS + 2) && (nbuf != NIL)) {
            printf("¡¡Lista hash %d CORRUPTA!!\n", nlista);
            bc_print();
            break;
         }
      } //while
      printf("Cont=%d. Tam=%d\n", cont, cache->tamHash[nlista]);
   }

   printf("\nDormidos para cualquier buffer: [");
   for (int i = 0; i < cache->nslept; i++)
      printf("%d, ", cache->bc_slept[i]);

   printf("]\n");
}


void check_corruption(const char * situacion) { //comprueba corrupcion de cache. si esta corrupta: programa termina con mensaje
   //recorre lista libre
   int nbuf = cache->freeStart;
   int cont = 0; //contador para comprobar corrupcion de la buffercache
   
   //comprueba tamaño total de freelist
   if (cache->tamFree > MAX_BUFFERS) {
      printf("CHECK CORRUPTION: pid = %d. %s: ¡¡FREELIST CORRUPTA!!\n", getpid(), situacion);
      bc_print();
      exit(0);
   }
   
   while (1) { //recorre freelist
      nbuf = cache->bufs[nbuf].freeListNext;
      cont++;

      if (nbuf == cache->freeStart) //ya ha dado la vuelta. aqui tambien se contempla si esta vacia
         break;

      if ((cont > MAX_BUFFERS + 2) || (nbuf == NIL)) { //freelist es circular, con lo que no deberia haber NIL
         printf("CHECK CORRUPTION: pid = %d. %s: ¡¡FREELIST CORRUPTA!!\n", getpid(), situacion);
         bc_print();
         exit(0);
         //break;
      }
   }
   
   int sumaHash = 0; //para comprobar tamaño total de hash
   
   //recorre hash
   for (int nlista = 0; nlista < MAX_HASH; nlista++)  { //recorre listas hash
      nbuf = cache->hashStart[nlista];
      cont = 0;
      sumaHash += cache->tamHash[nlista];
      while (nbuf != NIL) { //hasta fin de lista hash
         nbuf = cache->bufs[nbuf].hashNext;
         cont++;
         if ((cont > MAX_BUFFERS + 2) && (nbuf != NIL)) {
            printf("CHECK CORRUPTION: pid = %d. %s: ¡¡Lista hash %d CORRUPTA!!\n", getpid(), situacion, nlista);
            bc_print();
            exit(0);
            //break;
         }
      }
   }
   
   /*if (sumaHash > MAX_BUFFERS + 2) {
      printf("CHECK CORRUPTION: pid = %d. %s: ¡¡Hash CORRUPTA: se excede de tamaño total!!\n", getpid(), situacion);
      bc_print();
      exit(0);
   }*/

}

